/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.osdtsystem.estimator;

import org.osdtsystem.utils.Storage;
import org.osdtsystem.dataset.Dataset;
import org.osdtsystem.dataset.DatasetRaw;
import org.osdtsystem.dataset.View;

/**
 * Abstract estimator class.
 * @author Matthias Buch-Kromann
 */
public abstract class AbstractEstimator implements Estimator {
    final Storage storage;
    DatasetRaw trainingData = null;
    DatasetRaw addedData = null;
    final View view;
    boolean IGNORE_TRAINING_DATA = false;

    public AbstractEstimator(Storage storage, View view, Dataset trainingData) {
        this.storage = storage;
        this.view = view;
        this.trainingData = trainingData.emptyDataset();
        setTrainingData(trainingData);
    }

    public final Storage storage() {
        return storage;
    }

    @Override
    public View view() {
        return view;
    }

    @Override
    public final Dataset trainingData() {
        return trainingData;
    }

    @Override
    public final void clearTrainingData() {
        trainingData = trainingData.emptyDataset();
        addedData = trainingData.emptyDataset();
    }

    @Override
    public final void setTrainingData(Dataset dataset) {
        clearTrainingData();
        addTrainingData(dataset);
    }

    @Override
    public final void addTrainingData(Dataset dataset) {
        // Check dimensionality of dataset
        if (dataset.dimensionality() != trainingData.dimensionality())
            throw new IllegalArgumentException("New training data do not have the same dimensionality as the old training data");

        // Add training data if the model supports training
        if (! IGNORE_TRAINING_DATA) {
            for (Integer identifier : dataset)
                addedData.append(dataset.dataPoint(identifier));
        }
    }

    private final void updateDatasets() {
        // Update datasets
        for (Integer identifier : addedData)
            trainingData.append(addedData.dataPoint(identifier));
        addedData = trainingData.emptyDataset();
    }

    @Override
    public final void retrain() {
        // Retrain
        boolean retrained = retrain(trainingData, addedData);

        // Update datasets
        updateDatasets();

        // Call train() instead if the model was not retrained properly
        if (! retrained) {
            clearParameters();
            train(trainingData);
        }
    }

    @Override
    public void train() {
        // Update datasets and clear parameters
        updateDatasets();
        clearParameters();

        // Train by means of train(...) or retrain(...)
        boolean trained = train(trainingData);
        if (! trained)
            retrain(trainingData.emptyDataset(), trainingData);
    }

    /**
     * Retrain the estimator incrementally. The old training data and the new
     * training data are passed as arguments.
     * An estimator is allowed to avoid implementing retraining by creating a dummy
     * implementation which returns <code>false</code>. Calls to <code>retrain()</code>
     * will use this method to retrain the estimator if it returns <code>true</code>,
     * or fall back to non-incremental training if training has been implemented. If both training
     * and retraining are implemented, retraining is intended for fast incremental model
     * updates, whereas training is intended for more slow and thorough model reestimation.
     * @param trainingData the training data
     * @param newData the new training data
     * @return <code>true</code> if the incremental retraining was successful, <code>false</code>
     * otherwise.
     */
    abstract boolean retrain(Dataset oldTrainingData, Dataset newData);

    /**
     * Train the estimator from scratch. The training data are passed as an argument.
     * An estimator is allowed to avoid implementing training by creating a dummy
     * implementation which returns <code>false</code>. Calls to <code>train()</code>
     * will use this method to train the estimator if it returns <code>true</code>,
     * or fall back to the retraining implementation if it exists. If both training
     * and retraining are implemented, retraining is intended for fast incremental model
     * updates, whereas training is intended for more slow and thorough model reestimation.
     * @param trainingData the training data
     * @return <code>true</code> if the training was successful, <code>false</code>
     * otherwise.
     */
    abstract boolean train(Dataset trainingData);
}
